package com.andova.update;

import static android.content.Context.DOWNLOAD_SERVICE;

import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;

import com.andova.common.R;
import com.andova.ext.LogExtKt;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
import java.util.regex.PatternSyntaxException;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class ApkUpdater {
    private long mDownloadId;
    private TextView tvUpdate;
    private TextView tvMessage;
    private TextView tvProgress;
    private TextView tvBrowser;
    private Disposable mDisposable;
    private ProgressBar pbProgress;
    private LinearLayout llProgress;
    private AlertDialog mDownloadDialog;
    private DownloadManager mDownloadManager;
    @SuppressWarnings("WeakerAccess")
    public static final String APK_MIME_TYPE = "application/vnd.android.package-archive";

    private final Builder mBuilder;
    private final WeakReference<Context> mRefContext;

    public ApkUpdater(@NonNull Context context, @NonNull Builder builder) {
        mBuilder = builder;
        mRefContext = new WeakReference<>(context);
    }

    public static void clear(@NonNull Context context) {
        File file = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
        if (file == null) return;
        File[] files = file.listFiles();
        if (files == null) return;
        for (File f : files) LogExtKt.ai("delete file " + f.getPath() + " " + f.delete());
    }

    private int remoteVersionCode(@Nullable String versionName) {
        if (versionName == null) return -1;
        String[] versions;
        try {
            versions = versionName.split("\\.");
        } catch (PatternSyntaxException e) {
            e.printStackTrace();
            return -1;
        }
        if (versions.length < 3) return -1;
        for (int i = 1; i < 3; i++) {
            try {
                if (Integer.parseInt(versions[i]) < 10) {
                    versions[i] = "0" + versions[i];
                }
            } catch (NumberFormatException e) {
                e.printStackTrace();
                return -1;
            }
        }
        int versionCode;
        try {
            StringBuilder str = new StringBuilder();
            for (int i = 0; i < 3; i++) {
                str.append(versions[i]);
            }
            versionCode = Integer.parseInt(str.toString());
        } catch (NumberFormatException e) {
            e.printStackTrace();
            return -1;
        }
        return versionCode;
    }

    private String versionName() {
        PackageInfo info;
        try {
            Context context = mRefContext.get();
            if (context == null) return "0";
            info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return "0";
        }
        return info.versionName.substring(0, info.versionName.lastIndexOf('.'));
    }

    public boolean needUpdate(@Nullable String versionName) {
        if (TextUtils.isEmpty(versionName)) return false;
        PackageInfo info;
        try {
            Context context = mRefContext.get();
            if (context == null) return false;
            info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return false;
        }
        return info != null && remoteVersionCode(versionName) > info.versionCode;
    }

    private void release() {
        if (mDisposable != null) mDisposable.dispose();
    }

    public void asyncUpdate(String versionName) {
        if (!needUpdate(versionName)) return;
        if (mBuilder.silentInstall) {
            Context cxt = mRefContext.get();
            if (cxt == null) return;
            try {
                download(cxt, mBuilder.url);
                if (mBuilder.listener != null) mBuilder.listener.onReady(true);
            } catch (IllegalArgumentException e) {
                if (mBuilder.listener != null) mBuilder.listener.onError(e);
            }
        } else showUpdateUI();
    }

    public boolean isShowing() {
        if (mDownloadDialog == null) return false;
        return mDownloadDialog.isShowing();
    }

    public void dismiss() {
        if (mDownloadDialog == null) return;
        mDownloadDialog.dismiss();
    }

    private void showUpdateUI() {
        if (mDownloadDialog == null) {
            Context context = mRefContext.get();
            if (context == null) return;
            View view = LayoutInflater.from(context).inflate(R.layout.dig_download_progress, null);
            mDownloadDialog = new AlertDialog.Builder(context)
                    .setView(view)
                    .setCancelable(!mBuilder.forcedUpgrade)
                    .setOnCancelListener(dialog -> {
                        if (mBuilder.listener == null) return;
                        mBuilder.listener.onCancel();
                    })
                    .create();
            tvUpdate = view.findViewById(R.id.tv_update);
            tvMessage = view.findViewById(R.id.tv_message);
            tvProgress = view.findViewById(R.id.tv_progress);
            tvBrowser = view.findViewById(R.id.tv_browser);
            pbProgress = view.findViewById(R.id.pb_progress);
            llProgress = view.findViewById(R.id.ll_progress);
            tvMessage.setText(mBuilder.content);
            if (mBuilder.enableBrowser) {
                tvBrowser.setVisibility(View.VISIBLE);
            } else {
                tvBrowser.setVisibility(View.GONE);
            }
            tvUpdate.setOnClickListener(v -> {
                Context cxt = mRefContext.get();
                if (cxt == null) return;
                try {
                    download(cxt, mBuilder.url);
                    if (mBuilder.listener != null) mBuilder.listener.onReady(true);
                } catch (IllegalArgumentException e) {
                    if (mBuilder.listener != null) mBuilder.listener.onError(e);
                    Toast.makeText(cxt, e.getMessage(), Toast.LENGTH_SHORT).show();
                    return;
                }
                tvMessage.setText("");
                tvProgress.setText("");
                pbProgress.setProgress(0);
                tvUpdate.setVisibility(View.GONE);
                tvBrowser.setVisibility(View.GONE);
                llProgress.setVisibility(View.VISIBLE);
            });
            tvBrowser.setOnClickListener(v -> {
                Context cxt = mRefContext.get();
                if (cxt == null) return;
                browser(cxt, mBuilder.url);
            });
        }
        mDownloadDialog.show();
    }

    private void browser(Context context, String url) {
        Intent intent = new Intent();
        intent.setData(Uri.parse(url));//Url 就是你要打开的网址
        intent.setAction(Intent.ACTION_VIEW);
        if (intent.resolveActivity(context.getPackageManager()) == null) {
            if (mBuilder.listener != null) mBuilder.listener.onError(new ActivityNotFoundException());
            return;
        }
        if (mBuilder.listener != null) mBuilder.listener.onReady(false);
        context.startActivity(intent); //启动浏览器
    }

    private void downloadDialog(Context context, int currentSize, int totalSize, int state) {
        int percent = (int) ((float) currentSize / (float) totalSize * 100);
        if (!mBuilder.silentInstall) {
            pbProgress.setProgress(percent);
            tvProgress.setText(context.getString(R.string.format_download_progress, percent));
            tvMessage.setText(context.getString(R.string.format_download_size, size(currentSize), size(totalSize)));
        }
        switch (state) {
            case DownloadManager.STATUS_SUCCESSFUL:
                release();
                String path = getRealFilePath(context, mDownloadManager.getUriForDownloadedFile(mDownloadId));
                if (mBuilder.listener != null) mBuilder.listener.onSuccess(this, path);
                if (!mBuilder.silentInstall) install(context, path);
                break;
            case DownloadManager.STATUS_FAILED:
                release();
                if (mBuilder.listener != null) mBuilder.listener.onFailure(this);
                if (!mBuilder.silentInstall) {
                    tvMessage.setText(mBuilder.content);
                    llProgress.setVisibility(View.GONE);
                    tvUpdate.setVisibility(View.VISIBLE);
                }
                if (mBuilder.enableBrowser) tvBrowser.setVisibility(View.VISIBLE);
                break;
        }
    }

    private void download(final Context context, String apkUrl) throws IllegalArgumentException {
        mDownloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
        request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, context.getString(R.string.app_name) + ".apk");
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setMimeType(APK_MIME_TYPE);
        mDownloadId = mDownloadManager.enqueue(request);
        release();
        mDisposable = Observable.interval(500L, TimeUnit.MILLISECONDS)
                .map(t -> getBytesAndStatus())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(ints -> downloadDialog(context, ints[0], ints[1], ints[2]), throwable -> {
                    if (mBuilder.listener != null) mBuilder.listener.onError(throwable);
                });
    }

    /**
     * 通过query查询下载状态，包括已下载数据大小，总大小，下载状态
     */
    private int[] getBytesAndStatus() {
        int[] bytesAndStatus = new int[]{
                -1, -1, 0
        };
        if (mDownloadManager == null) return bytesAndStatus;
        DownloadManager.Query query = new DownloadManager.Query().setFilterById(mDownloadId);
        try (Cursor cursor = mDownloadManager.query(query)) {
            if (cursor != null && cursor.moveToFirst()) {
                //已经下载文件大小
                bytesAndStatus[0] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
                //下载文件的总大小
                bytesAndStatus[1] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
                //下载状态
                bytesAndStatus[2] = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
            }
        } catch (Exception e) {
            if (mBuilder.listener != null) mBuilder.listener.onError(e);
        }
        return bytesAndStatus;
    }

    private String size(int size) {
        if (size < 1024) {
            return size + "B";
        }
        if (size < 1024 * 1024) {
            return size / 1024 + "KB";
        }
        return size / 1024 / 1024 + "MB";
    }

    private void install(Context context, @Nullable String path) {
        if (TextUtils.isEmpty(path)) return;
        Uri downloadFileUri = mDownloadManager.getUriForDownloadedFile(this.mDownloadId);
        if (downloadFileUri == null) return;
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setDataAndType(downloadFileUri, APK_MIME_TYPE);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.parse("file://" + path), APK_MIME_TYPE);
        }
        if (intent.resolveActivity(context.getPackageManager()) == null) {
            Toast.makeText(context, context.getString(R.string.msg_open_install_page_fail), Toast.LENGTH_SHORT).show();
            if (mBuilder.listener != null) mBuilder.listener.onError(new ActivityNotFoundException());
            return;
        }
        context.startActivity(intent);
    }

    @Nullable
    String getRealFilePath(final Context context, final Uri uri) {
        if (uri == null) return null;
        final String scheme = uri.getScheme();
        String data = null;
        if (scheme == null)
            data = uri.getPath();
        else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
            data = uri.getPath();
        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
            Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
            if (null != cursor) {
                if (cursor.moveToFirst()) {
                    int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
                    if (index > -1) {
                        data = cursor.getString(index);
                    }
                }
                cursor.close();
            }
        }
        return data;
    }

    public static class Builder {
        @NonNull
        String url = "";
        @NonNull
        String content = "";
        boolean enableBrowser = false;
        boolean forcedUpgrade = false;
        boolean silentInstall = false;
        @Nullable
        ApkUpdateListener listener = null;

        public Builder() {
        }

        public Builder(@NonNull String url,
                       @NonNull String content,
                       boolean enableBrowser,
                       boolean forcedUpgrade,
                       @Nullable ApkUpdateListener listener) {
            this.url = url;
            this.content = content;
            this.enableBrowser = enableBrowser;
            this.forcedUpgrade = forcedUpgrade;
            this.listener = listener;
        }

        public Builder(@NonNull String url,
                       @NonNull String content,
                       boolean enableBrowser,
                       boolean forcedUpgrade,
                       boolean silentInstall,
                       @Nullable ApkUpdateListener listener) {
            this.url = url;
            this.content = content;
            this.enableBrowser = enableBrowser;
            this.forcedUpgrade = forcedUpgrade;
            this.silentInstall = silentInstall;
            this.listener = listener;
        }
    }

    public interface ApkUpdateListener {
        void onCancel();

        void onReady(boolean direct);

        void onError(Throwable throwable);

        void onSuccess(ApkUpdater apkUpdater, @Nullable String path);

        void onFailure(ApkUpdater apkUpdater);
    }
}